package net.csdn.business.auth.config;

import lombok.extern.slf4j.Slf4j;
import net.csdn.business.common.constant.RedisConstants;
import net.csdn.business.common.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author likun
 * @date 2022/6/8 10:25
 */
@EnableCaching
@Configuration
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {
    @Autowired
    private AuthConfigData configData;

    @Bean
    @SuppressWarnings({"all"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

    @Bean
    @SuppressWarnings({"all"})
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        // 指定 key 策略
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(factory),
                // 设置默认过期时间以及默认配置
                this.getRedisCacheConfigurationWithTtl(configData.getRedisDefaultTimeOut()),
                // 针对每个key设置缓存时间以及序列话策略
                this.getRedisCacheConfigurationMap()
        );
    }

    @Bean
    @SuppressWarnings({"all"})
    public RedisCache redisCache(@Autowired RedisTemplate redisTemplate) {
        return new RedisCache(redisTemplate, configData.getRedisKeyPrefix());
    }

    /**
     * 针对每个key设置缓存时间以及序列话策略
     */
    @SuppressWarnings({"all"})
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        // 自定义设置缓存时间
        for (Map.Entry<String, Integer> entry : RedisConstants.getKeyToTimeout().entrySet()) {
            redisCacheConfigurationMap.put(entry.getKey(), this.getRedisCacheConfigurationWithTtl(entry.getValue()));
        }
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())
                ).entryTtl(Duration.ofSeconds(seconds)).prefixCacheNameWith(configData.getRedisKeyPrefix() + RedisConstants.SEP);

        return redisCacheConfiguration;
    }

    /**
     * 解决因为Redis服务器挂调或者无法连接，导致接口不可用
     *
     * @return
     */
    @Override
    public CacheErrorHandler errorHandler() {
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {

            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.error("@@@@ redis异常-{}", "handleCacheGetError");
                RedisErrorException(exception, key);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.error("@@@@ redis异常-{}", "handleCachePutError");
                RedisErrorException(exception, key);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                log.error("@@@@ redis异常-{}", "handleCacheEvictError");
                RedisErrorException(exception, key);
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                log.error("@@@@ redis异常-{}", "handleCacheClearError");
                RedisErrorException(exception, null);
            }
        };
        return cacheErrorHandler;
    }

    /**
     * redis异常处理
     *
     * @param exception
     * @param key
     */
    protected void RedisErrorException(Exception exception, Object key) {
        log.error("@@@@ redis异常：key={}, exception={}", key, exception.getMessage());
    }

}